package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.R;
import com.atguigu.entity.to.es.SkuESModel;
import com.atguigu.gulimall.search.config.GulimallSearchConfig;
import com.atguigu.gulimall.search.constant.ESConstant;
import com.atguigu.gulimall.search.entity.vo.AttrResponseVo;
import com.atguigu.gulimall.search.entity.vo.BrandVo;
import com.atguigu.gulimall.search.entity.vo.SearchParamVo;
import com.atguigu.gulimall.search.entity.vo.SearchResultVo;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ProductFeignService productFeignService;

    @Override
    public SearchResultVo search(SearchParamVo paramVo) {
        //动态构建出查询需要的DSL语句，见dsl.txt

        SearchResultVo searchResult = null;

        //1 准备检索请求
        SearchRequest searchRequest = buildSearchRequest(paramVo);

        try {
            //2 执行检索请求
            SearchResponse response = client.search(searchRequest, GulimallSearchConfig.COMMON_OPTIONS);

            //3 分析详情请求 分装成我们需要的格式
            searchResult = buildSearchResult(response,paramVo);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return searchResult;
    }

    /**
     * 准备检索请求
     * paramVo
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParamVo paramVo) {

        SearchSourceBuilder builder = new SearchSourceBuilder();//用来构建DSL语句的
        /**
         * 查询条件：模糊匹配，过滤（按属性，分类，品牌，价格分区，库存）
         */
        //用QueryBuilders创建出一个builder，带s的都是工具类
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.1构建must 根据关键字模糊匹配
        if (!StringUtils.isEmpty(paramVo.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",paramVo.getKeyword()));
        }
        //1.2 构建filter
        if (paramVo.getCatalog3Id() != null){
            //按3级分类id过滤
            boolQuery.filter(QueryBuilders.termQuery("catalogId",paramVo.getCatalog3Id()));
        }
        //按品牌id过滤
        if (paramVo.getBrandId() != null && paramVo.getBrandId().size() > 0){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",paramVo.getBrandId()));
        }
        //按属性过滤
        if (paramVo.getAttrs() != null && paramVo.getAttrs().size() > 0){
            //每一个都必须生成一个嵌入式的查询
            for (String attr : paramVo.getAttrs()) {
                //attrs=1_5寸:8寸&2_16G:8G
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                //attr=1_5寸:8寸
                String[] s = attr.split("_");
                String attrId = s[0];
                String attrValue = s[1];
                String[] attrValues = attrValue.split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                //每一个都必须生成一个嵌入式的查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }

        //按库存是否有过滤
        boolQuery.filter(QueryBuilders.termQuery("hasStock",(paramVo.getHasStock() != null && paramVo.getHasStock() == 1)));

        //按价格区间来过滤
        if (!StringUtils.isEmpty(paramVo.getSkuPrice())){
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            //0_500 _500 500_
            String[] s = paramVo.getSkuPrice().split("_");
            if (s.length == 2){
                //区间
                rangeQuery.gte(s[0]).lte(s[1]);
            }else {
                if (paramVo.getSkuPrice().startsWith("_")){
                    rangeQuery.lte(s[0]);
                }else if(paramVo.getSkuPrice().endsWith("_")){
                    rangeQuery.gte(s[0]);
                }
            }
            boolQuery.filter(rangeQuery);
        }

        //分装这个查询条件
        builder.query(boolQuery);

        /**
         * 排序，分页，高亮
         */
        //排序 sort=hotScore_asc/desc
        if (!StringUtils.isEmpty(paramVo.getSort())){
            String[] s = paramVo.getSort().split("_");

            SortOrder asc;
            if (s[1].equalsIgnoreCase("asc")){
                asc = SortOrder.ASC;
            }else {
                asc = SortOrder.DESC;
            }
            builder.sort(s[0],asc);
        }
        //分页
        builder.from((paramVo.getPageNum() - 1) * ESConstant.PRODUCT_PAGESIZE);
        builder.size(ESConstant.PRODUCT_PAGESIZE);

        //高亮
        if (!StringUtils.isEmpty(paramVo.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            builder.highlighter(highlightBuilder);
        }

        /**
         * 聚合分析
         */
        //品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId");
        brand_agg.size(50);
        //品牌聚合的子聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(20));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(20));
        builder.aggregation(brand_agg);

        //分类聚合
        TermsAggregationBuilder catelog_agg = AggregationBuilders.terms("catelog_agg");
        catelog_agg.field("catalogId");
        catelog_agg.size(20);
        catelog_agg.subAggregation(AggregationBuilders.terms("catelog_name_agg").field("catalogName").size(1));
        builder.aggregation(catelog_agg);

        //属性聚合
        NestedAggregationBuilder nested = AggregationBuilders.nested("attr_agg", "attrs");
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg");
        attr_id_agg.field("attrs.attrId");
        attr_id_agg.size(10);
        //子聚合attr_name_agg
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(10));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(10));
        nested.subAggregation(attr_id_agg);
        builder.aggregation(nested);

        String s = builder.toString();
        System.out.println(s);

        SearchRequest searchRequest = new SearchRequest(new String[]{ESConstant.PRODUCT_INDEX},builder);

        return searchRequest;
    }

    /**
     * 构建结果数据
     * @param response
     * @return
     */
    private SearchResultVo buildSearchResult(SearchResponse response,SearchParamVo paramVo) {

        SearchResultVo searchResult = new SearchResultVo();
        SearchHits hits = response.getHits();
        //获取命中的记录
        //从命中信息中获取
        List<SkuESModel> esModelList = new ArrayList<SkuESModel>();
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1) {
            String sourceAsString = hit.getSourceAsString();
            SkuESModel model = new SkuESModel();
            SkuESModel esModel = JSON.parseObject(sourceAsString, SkuESModel.class);

            //判断是否有高亮
            if (!StringUtils.isEmpty(paramVo.getKeyword())){
                HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                String string = skuTitle.getFragments()[0].string();
                esModel.setSkuTitle(string);
            }
            esModelList.add(esModel);
        }
        //1 返回的所有查询到到商品
        searchResult.setProducts(esModelList);

        //以下数据从聚合信息中获取
        //2 当前商品所涉及到到属性信息
        List<SearchResultVo.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        List<? extends Terms.Bucket> attrBuckets = attr_id_agg.getBuckets();
        for (Terms.Bucket attrBucket : attrBuckets) {
            SearchResultVo.AttrVo attrVo = new SearchResultVo.AttrVo();

            //属性Id
            long attrId = attrBucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);

            //获取子聚合,属性名字
            ParsedStringTerms attr_name_agg = attrBucket.getAggregations().get("attr_name_agg");
            String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);

            //获取子聚合,属性值
            ParsedStringTerms attr_value_agg = attrBucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attr_value_agg.getBuckets().stream().map(item -> {
                String keyAsString = ((Terms.Bucket) item).getKeyAsString();
                return keyAsString;
            }).collect(Collectors.toList());
            attrVo.setAttrValue(attrValues);

            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);

        //3 当前商品所涉及到到品牌信息
        List<SearchResultVo.BrandVo> brandVos = new ArrayList<>();
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            SearchResultVo.BrandVo brandVo = new SearchResultVo.BrandVo();
            //品牌id
            long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setBrandId(brandId);

            //获取子聚合,品牌名字
            ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
            String branName = brand_name_agg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(branName);

            //获取子聚合,品牌图片
            ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
            String branImg = brand_img_agg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandImg(branImg);

            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);

        //4 当前商品所涉及到到分类信息
        List<SearchResultVo.CatalogVo> catalogVos = new ArrayList<>();
        ParsedLongTerms catelog_agg = response.getAggregations().get("catelog_agg");
        List<? extends Terms.Bucket> catelog_aggBuckets = catelog_agg.getBuckets();
        for (Terms.Bucket catelog_aggBucket : catelog_aggBuckets) {
            SearchResultVo.CatalogVo catalogVo = new SearchResultVo.CatalogVo();
            String keyAsString = catelog_aggBucket.getKeyAsString();
            //分类id
            catalogVo.setCatalogId(Long.parseLong(keyAsString));

            //获取子聚合,提取出分类名字
            ParsedStringTerms catelog_name_agg = catelog_aggBucket.getAggregations().get("catelog_name_agg");
            String catalogName = catelog_name_agg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);


            catalogVos.add(catalogVo);
        }
        searchResult.setCatalogs(catalogVos);

        //5 分页信息
        //总记录数
        long total = hits.getTotalHits().value;
        searchResult.setTotal(total);
        //总页面
        Integer totalPages = (int)((total + ESConstant.PRODUCT_PAGESIZE - 1) / ESConstant.PRODUCT_PAGESIZE);
        searchResult.setTotalPages(totalPages);
        //当前页
        searchResult.setPageNum(paramVo.getPageNum());
        //页码集合
        List<Integer> navPages = new ArrayList<>();
        for (Integer i = 0; i < totalPages; i++) {
            navPages.add(i);
        }
        searchResult.setTotalNavs(navPages);

        //6.构建面包屑导航
        //遍历检索属性
        if (paramVo.getAttrs() != null && paramVo.getAttrs().size() > 0){
            //分析每一个传过来的属性
            List<SearchResultVo.NavVo> navVos = paramVo.getAttrs().stream().map(attr -> {
                //attrs=2_5寸：6寸
                SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();
                String[] s = attr.split("_");
                navVo.setNavValue(s[1]);
                //远程调用获取属性名称，失败了使用id做为名称
                R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                //分析请求参数时加入属性id
                searchResult.getAttrIds().add(Long.parseLong(s[0]));
                if (r.getCode() == 0){
                    AttrResponseVo attr1 = r.getData("attr", new TypeReference<AttrResponseVo>() {
                    });

                    navVo.setNavName(attr1.getAttrName());
                }else {
                    navVo.setNavName(s[0]);
                }

                //取消面包屑后跳转路径
                String queryString = paramVo.get_queryString();
                String replace = repleaseQueryString(attr, queryString,"attrs");
                navVo.setLink("http://search.gulimall.com/list.html?"+replace);

                return navVo;
            }).collect(Collectors.toList());

            searchResult.setNavVos(navVos);
        }

        //给品牌 分类添加面包屑导航
        if (paramVo.getBrandId() != null && paramVo.getBrandId().size()>0){
            List<SearchResultVo.NavVo> navVos = searchResult.getNavVos();
            SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();

            navVo.setNavName("品牌");
            //远程查询所有品牌
            R r = productFeignService.brandInfos(paramVo.getBrandId());
            if (r.getCode() == 0){
                List<BrandVo> brand = r.getData("brand", new TypeReference<List<BrandVo>>() {
                });
                //品牌名
                StringBuffer buffer = new StringBuffer();
                String replace = "";
                for (BrandVo brandVo : brand) {
                    buffer.append(brandVo.getBrandName() + ";");
                    String queryString = paramVo.get_queryString();
                    replace = repleaseQueryString(brandVo.getBrandId()+"", queryString,"brandId");
                }

                navVo.setNavValue(buffer.toString());
                navVo.setLink(replace);
            }
            navVos.add(navVo);
        }

        //TODU 分类 分类不需要取消导航，可以不拼url地址

        return searchResult;
    }

    private String repleaseQueryString(String attr, String queryString,String key) {
        //将点击删除的属性替换成空字符串
        //替换时有编码问题
        //编码问题有个浏览器兼容问题，空格会替换成加号,需要替换回去
        String encode = null;
        try {
            encode = URLEncoder.encode(attr, "UTF-8");
            encode = encode.replace("+","%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return queryString.replace("&"+key+"=" + encode, "");
    }


}
